package no.ntnu.item.arctis.runtime;

import java.io.PrintWriter;
import java.io.StringWriter;

public class Logger {
		
	public static final int DEBUG_ALL = 0;	
	public static final int DEBUG_INFO = 1;
	public static final int DEBUG_WARN = 2;
	public static final int DEBUG_ERROR = 3;
	
	private static final int DEBUG_LEVEL = DEBUG_WARN;
	
	private static Logger instance;
	
	public static synchronized Logger getLogger() {
		if(instance==null) instance = new Logger();
		return instance;
	}
	
	public static synchronized void setLogger(Logger logger) {
		if(instance!=null) throw new IllegalStateException("The logger is already initialized.");
		instance = logger;
	}
	
	/**
	 * A state machine discarded a message.
	 */
	public void discardedSignal(String signalName, String sender, String receiver, String stateMachineType, String state) {
		if(DEBUG_LEVEL>DEBUG_WARN) return;
		String message = " ! DISCARDED SIGNAL.  id: " + signalName + "\n"
					   + "                  sender: " + sender + "\n"
		               + "                receiver: " + receiver + "\n"
		               + "                 machine: " + stateMachineType + "\n"
		               + "                   state: " + state + "\n";
		System.err.println(message);
		Block.notifyListeners("Discarded Signal", message);
	}
	
	/**
	 * A state machine discarded a message, because the trigger is not known at all.
	 */
	public void unknownTrigger(String signalName, String sender, String receiver, String stateMachineType) {
		String message = " ! UNKNOWN TRIGGER.   id: " + signalName + "\n"
					   + "                  sender: " + sender + "\n"
					   + "                receiver: " + receiver + "\n"
					   + "                 machine: " + stateMachineType + "\n";
		System.err.println(message);
		Block.notifyListeners("Unknown Trigger", message);
	}
	
	/**
	 * A component was asked to deliver a message to a part that is not foreseen for this component.
	 */
	public void invalidPartName(String partName, String sender, String receiver, String componentType) {
		String message = " ! INVALID PART REQUESTED" + "\n"
		   			   + "                partname: " + partName + "\n"		
		   			   + "                  sender: " + sender + "\n"
		   			   + "                receiver: " + receiver + "\n"
		   			   + "          component type: " + componentType + "\n";
		System.err.println(message);
		Block.notifyListeners("Invalid Partname", message);
	}

	public void sessionCreationFailed(String partName, String stateMachineType) {
		String message = " ! SESSION CREATION FAILED" + "\n"
		   + "                partname: " + partName + "\n"		
			   + "   parent component type: " + stateMachineType + "\n";
		System.err.println(message);
		Block.notifyListeners("", message);
	}
	
	public void sessionCreated(String type, String sessionID, int instanceCount) {
		if(DEBUG_LEVEL>DEBUG_INFO) return;
		System.out.println("   SESSION CREATED");
		System.out.println("                    type: " + type);		
		System.out.println("              session id: " + sessionID);		
		System.out.println("               instances: " + instanceCount);		
	}
	
	public void sessionSelectionFailed(String type, String signalID, String sessionID) {
		String message = "   SESSION SELECTION FAILED" + "\n"
		               + "               signal id: " + signalID + "\n"		
			           + "                    type: " + type + "\n"
		               + "               sessionID: " + sessionID + "\n";	
		System.err.println(message);
		Block.notifyListeners("Session selection failed", message);
	}
	
	public void sessionTerminated(String type, String sessionID, int instanceCount) {
		if(DEBUG_LEVEL>DEBUG_INFO) return;
		System.out.println("   SESSION TERMINATED");
		System.out.println("                    type: " + type);		
		System.out.println("              session id: " + sessionID);		
		System.out.println("               instances: " + instanceCount);		
	}
	
	public void nestedSessionTerminated(String type, String sessionID, int instanceCount, String parentType, String parentSessionID) {
		if(DEBUG_LEVEL>DEBUG_INFO) return;
		System.out.println("   NESTED SESSION TERMINATED");
		System.out.println("                  parent: " + parentType + " " + parentSessionID);
		System.out.println("                    type: " + type);		
		System.out.println("              session id: " + sessionID);		
		System.out.println("               instances: " + instanceCount);		
	}
	
	public void communicationException(String message, Throwable t) {
		System.err.println(" ! COMMUNICATION EXCEPTION");
		System.err.println("                 message: " + message);
		t.printStackTrace();
	}
	
	public void messageDroppedDuringRouting(String sender, String receiver, String signalId, String comment) {
		if(DEBUG_LEVEL>DEBUG_WARN) return;
	    System.err.println(" ! DROPPED SIGNAL.    id: " + signalId);
        System.err.println("                  sender: " + sender);
        System.err.println("                receiver: " + receiver); 
        System.err.println("                 comment: " + comment); 
	}
	
	public void transition(String trigger, String states, String time, IStateMachine stateMachine) {
		if(DEBUG_LEVEL>DEBUG_ALL) return;
		System.out.println("   TRANSITION.   trigger: " + trigger);
        System.out.println("                  states: " + states);
        System.out.println("                    time: " + time); 
       	System.out.println("                 machine: " + getSTMString(stateMachine));
	}

	protected String getSTMString(IStateMachine stateMachine) {
		if(stateMachine.sessionAlias!=null) {
			return stateMachine.getClass().getSimpleName() 
       			+ " " + stateMachine.sessionID 
       			+ " " + " a.k.a. " + stateMachine.sessionAlias;
		} else if (stateMachine.sessionID!=null) {
			return stateMachine.getClass().getSimpleName() + " " + stateMachine.sessionID;
		} else {
			return stateMachine.getClass().getSimpleName();
		}
	}
	
	public void applicationErrorDuringFire(IStateMachine stateMachine, String state, String signalID, Throwable t) {
		String message = " ! APPLICATION ERROR. " + "\n"
		               + "                     stm: " + getSTMString(stateMachine) + "\n"
		               + "                   state: " + state + "\n"
		               + "                signalID: " + signalID + "\n"; 
        if(t!=null) t.printStackTrace();
        System.err.println(message + "\n\n" + getStackTrace(t));
        Block.notifyListeners("Application Error During Fire", message + "\n\n" + getStackTrace(t));
	}
	
	public void applicationErrorDuringFireInitial(IStateMachine stateMachine, Throwable t) {
		String message = " ! APPLICATION ERROR IN INITIAL TRANSITION. stm type: " + getSTMString(stateMachine) + "\n";
        if(t!=null) t.printStackTrace();
        System.err.println(message + "\n\n" + getStackTrace(t));
        Block.notifyListeners("Application Error During Fire Initial", message + "\n\n" + getStackTrace(t));
	}
	
	public void applicationErrorDuringFireTimer(IStateMachine stateMachine, String state, String timerID, Throwable t) {
		String message = " ! APPLICATION ERROR.      stm: " + getSTMString(stateMachine) + "\n"
		               + "                         state: " + state + "\n"
		               + "                       timerID: " + timerID + "\n"; 
        if(t!=null) t.printStackTrace();
        System.err.println(message + "\n\n" + getStackTrace(t));
        Block.notifyListeners("Application Error During Fire Timer", message + "\n\n" + getStackTrace(t));
	}
	
	public void applicationError(String explanation, Throwable t) {
		String message = " ! APPLICATION ERROR." + "\n"
		               + "               explanation: " + explanation + "\n";
        if(t!=null) t.printStackTrace();
        System.err.println(message + "\n\n" + getStackTrace(t));
        Block.notifyListeners("Application Error", message + "\n\n" + getStackTrace(t));
	}

	public void registerError(String message, String sender, String receiver, String signalID) {
		String m = " ! REGISTER ERROR. : " + message + "\n"
		         + "                         sender: " + sender + "\n"
		         + "                       receiver: " + receiver + "\n" 
   			   	 + "                       signalID: " + signalID + "\n";
        System.err.println(m);
        Block.notifyListeners("Register Error", m);
	}

	public void systemTerminated() {
		System.out.println("Shutting down runtime system. Good bye!");
	}

	public void blockingChoice() {
		System.err.println(" ! BLOCKING CHOICE. : ");
	}
	
	private String getStackTrace(Throwable t) {
		if(t==null) return "";
		StringWriter sw = new StringWriter();
		t.printStackTrace(new PrintWriter(sw));
		return sw.toString();
	}
}